﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace QLua.Extensions;

static class TypeExtensions
{
	public static bool HasMethod(this Type t, string name)
	{
		var op = t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
		return op.Any(m => m.Name == name);
	}

	public static bool HasAdditionOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_Addition");
	}

	public static bool HasSubtractionOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_Subtraction");
	}

	public static bool HasMultiplyOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_Multiply");
	}

	public static bool HasDivisionOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_Division");
	}

	public static bool HasModulusOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_Modulus");
	}

	public static bool HasUnaryNegationOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;
		// Unary - will always have only one version.
		var op = t.GetMethod("op_UnaryNegation", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
		return op != null;
	}

	public static bool HasEqualityOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;
		return t.HasMethod("op_Equality");
	}

	public static bool HasLessThanOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;

		return t.HasMethod("op_LessThan");
	}

	public static bool HasLessThanOrEqualOperator(this Type t)
	{
		if (t.IsPrimitive)
			return true;
		return t.HasMethod("op_LessThanOrEqual");
	}

	public static MethodInfo[] GetMethods(this Type t, string name, BindingFlags flags)
	{
		return t.GetMethods(flags).Where(m => m.Name == name).ToArray();
	}

	public static MethodInfo[] GetExtensionMethods(this Type type, string name, IEnumerable<Assembly> assemblies = null)
	{
		var types = new List<Type>();

		types.AddRange(type.Assembly.GetTypes().Where(t => t.IsPublic));

		if (assemblies != null)
		{
			foreach (Assembly item in assemblies)
			{
				if (item == type.Assembly)
					continue;
				types.AddRange(item.GetTypes().Where(t => t.IsPublic && t.IsClass && t.IsSealed && t.IsAbstract && !t.IsNested));
			}
		}

		var query = types
			.SelectMany(extensionType => extensionType.GetMethods(name, BindingFlags.Static | BindingFlags.Public),
				(extensionType, method) => new { extensionType, method })
			.Where(t => t.method.IsDefined(typeof(ExtensionAttribute), false))
			.Where(t =>
				t.method.GetParameters()[0].ParameterType == type ||
				 t.method.GetParameters()[0].ParameterType.IsAssignableFrom(type) ||
				 type.GetInterfaces().Contains(t.method.GetParameters()[0].ParameterType))
			.Select(t => t.method);

		return query.ToArray();
	}

	/// <summary>
	/// Extends the System.Type-type to search for a given extended MethodeName.
	/// </summary>
	/// <param name="t"></param>
	/// <param name="name"></param>
	/// <param name="assemblies"></param>
	/// <returns></returns>
	public static MethodInfo GetExtensionMethod(this Type t, string name, IEnumerable<Assembly> assemblies = null)
	{
		var mi = t.GetExtensionMethods(name, assemblies).ToArray();
		if (mi.Length == 0)
			return null;
		return mi[0];
	}
}
